from pathlib import Path
import urllib.parse
import os
import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider
from oss2.exceptions import OssError, ServerError, ClientError

# os.environ["OSS_ACCESS_KEY_ID"]="替换为自己的accessKeyId"
# os.environ["OSS_ACCESS_KEY_SECRET"]="替换为自己的accessKeySecret"
# os.environ["OSS_BUCKET_NAME"]="替换为自己的bucket名称"
# os.environ["OSS_ENDPOINT"]="oss-cn-beijing.aliyuncs.com"#选择你的bucket所在的地域节点

def upload_local_file_to_oss(
        local_file_path: str,
        oss_access_key_id: str = os.getenv("OSS_ACCESS_KEY_ID"),
        oss_access_key_secret: str = os.getenv("OSS_ACCESS_KEY_SECRET"),
        oss_bucket_name: str = os.getenv("OSS_BUCKET_NAME"),
        oss_endpoint: str = os.getenv("OSS_ENDPOINT"),
        oss_file_key_prefix: str = "dashscope-assets/",  # OSS文件存储的前缀路径
        is_private_bucket: bool = True,  # 是否为私有Bucket（推荐True，更安全）
        presign_expire_seconds: int = 3600  # 私有Bucket时，预签名URL的有效期（单位：秒，默认1小时）
) -> str:
    """
    将本地文件上传到阿里云OSS，并返回**可直接访问的URL**（兼容公开/私有Bucket）

    参数说明：
        local_file_path: 本地文件的绝对路径（如 "C:/audio/test.wav" 或 "/home/image/face.jpg"）
        oss_access_key_id: 阿里云AccessKey ID（默认从环境变量读取）
        oss_access_key_secret: 阿里云AccessKey Secret（默认从环境变量读取）
        oss_bucket_name: OSS Bucket名称（默认从环境变量读取）
        oss_endpoint: OSS地域节点（如 "oss-cn-hangzhou.aliyuncs.com"，默认从环境变量读取）
        oss_file_key_prefix: OSS中文件的存储前缀（如 "dashscope-assets/"，最终路径为前缀+文件名）
        is_private_bucket: 是否为私有Bucket（True=私有，返回预签名URL；False=公开，返回固定URL）
        presign_expire_seconds: 私有Bucket时，预签名URL的有效期（需为正整数，建议范围：60~86400）

    返回：
        可直接访问的文件URL（私有Bucket返回预签名URL，公开Bucket返回固定公网URL）

    异常：
        FileNotFoundError: 本地文件不存在或不是有效文件
        ValueError: 必要参数为空（如AccessKey、BucketName）或有效期非法
        OssError: OSS操作异常（如权限不足、文件过大、网络错误等）
    """
    # -------------------------- 1. 基础参数校验 --------------------------
    # 校验本地文件
    local_file = Path(local_file_path)
    if not local_file.exists() or not local_file.is_file():
        raise FileNotFoundError(f"本地文件不存在或不是有效文件：{local_file_path}")

    # 校验OSS必要参数（避免从环境变量读取失败）
    required_oss_params = {
        "OSS_ACCESS_KEY_ID": oss_access_key_id,
        "OSS_ACCESS_KEY_SECRET": oss_access_key_secret,
        "OSS_BUCKET_NAME": oss_bucket_name,
        "OSS_ENDPOINT": oss_endpoint
    }
    for param_name, param_value in required_oss_params.items():
        if not isinstance(param_value, str) or len(param_value.strip()) == 0:
            raise ValueError(f"参数'{param_name}'为空，请检查环境变量或手动传入")

    # 校验预签名有效期（仅私有Bucket时生效）
    if is_private_bucket and (not isinstance(presign_expire_seconds, int) or presign_expire_seconds <= 0):
        raise ValueError(f"私有Bucket时，'presign_expire_seconds'必须为正整数（当前值：{presign_expire_seconds}）")

    # -------------------------- 2. 初始化OSS客户端 --------------------------
    try:
        # 初始化认证（v2签名，适用于大部分场景；若需v4签名，可替换为 ProviderAuthV4）
        auth = oss2.Auth(oss_access_key_id.strip(), oss_access_key_secret.strip())
        # 初始化Bucket客户端（strip()避免参数前后空格导致的连接失败）
        bucket = oss2.Bucket(
            auth=auth,
            endpoint=oss_endpoint.strip(),
            bucket_name=oss_bucket_name.strip()
        )
    except (ClientError, ValueError) as e:
        raise OssError(f"OSS客户端初始化失败：{str(e)}") from e

    # -------------------------- 3. 上传本地文件到OSS --------------------------
    # 构造OSS文件的完整路径（key）：前缀 + 本地文件名（避免覆盖同名文件，可根据需求修改）
    oss_file_key = f"{oss_file_key_prefix.strip('/')}/{local_file.name}"  # 处理前缀末尾的"/"，避免重复

    try:
        # 上传文件（put_object_from_file：从本地文件路径上传）
        result = bucket.put_object_from_file(oss_file_key, local_file_path)
        # 校验上传结果（HTTP 200表示上传成功）
        if result.status != 200:
            raise OssError(f"文件上传失败，HTTP状态码：{result.status}，响应信息：{result.resp.text}")
        print(f"文件上传成功，OSS存储路径：{oss_file_key}")
    except (ServerError, ClientError) as e:
        raise OssError(f"OSS上传异常：{str(e)}（可能原因：权限不足、文件过大、Endpoint错误）") from e

    # -------------------------- 4. 生成可访问的URL --------------------------
    # 处理OSS文件路径中的特殊字符（如中文、空格），避免URL无效
    encoded_oss_key = urllib.parse.quote(oss_file_key, safe='/')  # safe='/'表示不转义路径分隔符

    if is_private_bucket:
        # 场景1：私有Bucket → 生成预签名URL（有效期内可访问，过期失效）
        try:
            presigned_url = bucket.sign_url(
                method='GET',  # 下载文件用GET请求
                key=oss_file_key,  # 注意：sign_url需传入原始key，而非转义后的key
                expires=presign_expire_seconds,
                slash_safe=True  # 不转义路径中的"/"，确保URL正确
            )
            print(f"生成私有Bucket预签名URL（有效期{presign_expire_seconds}秒）：{presigned_url}")
            return presigned_url
        except (ServerError, ClientError) as e:
            raise OssError(f"预签名URL生成失败：{str(e)}") from e
    else:
        # 场景2：公开Bucket → 拼接固定公网URL（永久可访问，需确保Bucket权限为"公开读写"）
        public_url = f"https://{oss_bucket_name.strip()}.{oss_endpoint.strip()}/{encoded_oss_key}"
        print(f"生成公开Bucket固定URL：{public_url}")
        return public_url

if __name__ == '__main__':
    upload_local_file_to_oss("test.png")
